Skip to content

fix: retry gemini empty parts response#6735

Open
Clhikari wants to merge 3 commits intoAstrBotDevs:masterfrom
Clhikari:fix:gemini-empty-parts-retry

Hidden character warning

The head ref may contain hidden characters: "fix\uff1agemini-empty-parts-retry"
Open

fix: retry gemini empty parts response#6735
Clhikari wants to merge 3 commits intoAstrBotDevs:masterfrom
Clhikari:fix:gemini-empty-parts-retry

Conversation

@Clhikari
Copy link
Contributor

@Clhikari Clhikari commented Mar 21, 2026

修复了 Gemini 提供商的一种异常返回场景:API 返回 finish_reason=STOP,但 candidate.content.parts 为空。

报错日志

[2026-03-17 23:02:12.585] [Core] [WARN] [v4.20.0] [sources.gemini_source:462]: 收到的 candidate.content.parts 为空: content=Content() citation_metadata=None finish_message=None token_count=None finish_reason=<FinishReason.STOP: 'STOP'> avg_logprobs=None grounding_metadata=None index=0 logprobs_result=None safety_ratings=[SafetyRating( 
  category=<HarmCategory.HARM_CATEGORY_HARASSMENT: 'HARM_CATEGORY_HARASSMENT'>,
  probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
), SafetyRating(
  category=<HarmCategory.HARM_CATEGORY_HATE_SPEECH: 'HARM_CATEGORY_HATE_SPEECH'>,
  probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
), SafetyRating(
  category=<HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 'HARM_CATEGORY_SEXUALLY_EXPLICIT'>,
  probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
), SafetyRating(
  category=<HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: 'HARM_CATEGORY_DANGEROUS_CONTENT'>,
  probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
)] url_context_metadata=None
[2026-03-17 23:02:12.587] [Core] [WARN] [v4.20.0] [runners.tool_loop_agent_runner:268]: Chat Model google_gemini_1/gemini-3-flash-preview request error: API 返回的 candidate.content.parts 为空。
Traceback (most recent call last):
  File "D:\aobo\open_source\AstrBot\astrbot\core\agent\runners\tool_loop_agent_runner.py", line 243, in _iter_llm_responses_with_fallback
    async for resp in self._iter_llm_responses(include_model=idx == 0):
  File "D:\aobo\open_source\AstrBot\astrbot\core\agent\runners\tool_loop_agent_runner.py", line 220, in _iter_llm_responses
    yield await self.provider.text_chat(**payload)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\aobo\open_source\AstrBot\astrbot\core\provider\sources\gemini_source.py", line 767, in text_chat
    return await self._query(payloads, func_tool)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\aobo\open_source\AstrBot\astrbot\core\provider\sources\gemini_source.py", line 591, in _query
    llm_response.result_chain = self._process_content_parts(
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\aobo\open_source\AstrBot\astrbot\core\provider\sources\gemini_source.py", line 463, in _process_content_parts
    raise Exception("API 返回的 candidate.content.parts 为空。")
Exception: API 返回的 candidate.content.parts 为空。

在这个改动之前,Gemini 的非流式路径会把这种响应直接当成致命错误处理,导致当前 agent step 提前中断,在依赖工具调用的流程中会直接打断后续执行。这个改动只针对这类明确的上游异常响应增加有限重试,让临时性的空响应可以恢复,同时不改变持续失败时的现有报错行为。

Modifications / 改动点

  • 修改了 astrbot/core/provider/sources/gemini_source.py,在 Gemini 非流式 _query() 中处理 finish_reason=STOPcandidate.content.parts 为空的情况。

  • 增加了带短暂退避的有限重试逻辑。

  • 当重试耗尽后,仍然沿用现有失败逻辑,不扩大行为变更范围。

  • 新增了回归测试 tests/test_gemini_source.py,验证首次空响应后可以在后续重试中恢复成功。

  • 新增 tool-call 场景测试,验证 provider 在空响应后重试,仍能正确恢复并产出函数调用信息。

  • This is NOT a breaking change. / 这不是一个破坏性变更。

本地已执行以下验证步骤:

uv run ruff format .
uv run ruff check .
uv run python -m pytest tests/test_gemini_source.py

测试结果:

2 passed

新增测试覆盖了以下场景:

  • 第一次 Gemini 响应返回空的 candidate.content.parts
  • provider 不会立即失败,而是进入重试
  • 第二次响应返回正常文本内容,请求成功恢复
  • 第二次响应返回 function_call,请求也能成功恢复,并正确生成工具调用字段

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Handle Gemini non-streaming responses that stop with empty content parts by retrying instead of failing immediately.

Bug Fixes:

  • Prevent Gemini provider from treating STOP responses with empty candidate.content.parts as fatal errors by introducing limited retries.

Enhancements:

  • Add bounded retry logic with short backoff for Gemini STOP responses that contain empty content parts while preserving existing failure behavior after retries are exhausted.

Tests:

  • Add regression tests covering recovery from initial empty Gemini responses for both plain text replies and tool-call scenarios.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 21, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在增强系统与 Gemini API 交互的稳定性。它通过引入一个智能重试机制,解决了 Gemini API 在特定情况下返回空内容但指示完成的边缘问题。这一改进确保了即使在 API 出现瞬时异常响应时,系统也能继续正常运行,从而提升了用户体验和应用的可靠性,而不会改变持续失败时的错误处理行为。

Highlights

  • 修复 Gemini 空内容响应: 解决了 Gemini API 在返回 finish_reason=STOP 时,candidate.content.parts 却为空的异常场景,此前这会导致致命错误。
  • 引入有限重试机制: 为上述特定异常情况增加了带有短暂退避的有限重试逻辑,以提高系统的健壮性,允许临时性空响应恢复。
  • 新增回归测试: 添加了新的测试用例,验证了在首次空响应后,系统能够通过重试成功恢复,包括文本生成和工具调用两种场景。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label Mar 21, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 修复了 Gemini API 在特定场景下返回空响应的问题。您通过引入有限重试机制,优雅地处理了这种瞬时错误,有效增强了系统的健壮性。代码改动清晰,并且新增的单元测试覆盖了核心恢复场景,确保了改动的正确性。

代码整体质量很高,我只有一个关于代码可维护性的建议:将重试逻辑中的“魔法数字”(如重试次数和延迟时间)提取为类级别的常量,以便于未来统一管理和修改。

Comment on lines +531 to +532
empty_parts_retry_count = 0
max_empty_parts_retries = 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

为了提高代码的可读性和可维护性,建议将最大重试次数 max_empty_parts_retries 和重试延迟(在第 579 行硬编码为 0.2)定义为类级别的常量。

例如,在 ProviderGoogleGenAI 类的顶部定义:

class ProviderGoogleGenAI(Provider):
    _MAX_EMPTY_PARTS_RETRIES = 3
    _EMPTY_PARTS_RETRY_DELAY_S = 0.2
    # ...

然后在 _query 方法中通过 self._MAX_EMPTY_PARTS_RETRIESself._EMPTY_PARTS_RETRY_DELAY_S 来引用它们。

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The retry/backoff behavior for empty candidate.content.parts is fully hardcoded (max 3 retries, 0.2s sleep); consider making retry count and delay configurable via the provider config so operators can tune reliability vs latency for different deployments.
  • In the STOP+empty-parts retry branch, the warning log currently dumps the entire candidate object, which may be large or contain sensitive data; logging the response_id and key fields instead would keep logs more compact and safer while still being debuggable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The retry/backoff behavior for empty `candidate.content.parts` is fully hardcoded (max 3 retries, 0.2s sleep); consider making retry count and delay configurable via the provider config so operators can tune reliability vs latency for different deployments.
- In the STOP+empty-parts retry branch, the warning log currently dumps the entire `candidate` object, which may be large or contain sensitive data; logging the `response_id` and key fields instead would keep logs more compact and safer while still being debuggable.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant